import sys


class ModifiedFileAnalyzer:

    def __init__(self, modified_file):
        self.old_to_new_line_offset = None
        self.new_to_old_line_offset = None
        self.delete_blocks = None
        self.add_blocks = None
        self.old_lines_content = None
        self.new_lines_content = None
        self.modified_file = modified_file

    def analyze(self):
        self.add_blocks = self.get_line_block(self.modified_file.diff_parsed.get("added"))
        self.delete_blocks = self.get_line_block(self.modified_file.diff_parsed.get("deleted"))
        self.new_to_old_line_offset = self.get_from_to_offset_map(self.add_blocks, self.delete_blocks)
        self.old_to_new_line_offset = self.get_from_to_offset_map(self.delete_blocks, self.add_blocks)
        if self.modified_file.source_code is not None:
            self.new_lines_content = self.modified_file.source_code.split("\n")
        else:
            self.new_lines_content = []
        if self.modified_file.source_code_before is not None:
            self.old_lines_content = self.modified_file.source_code_before.split("\n")
        else:
            self.old_lines_content = []

    def get_modified_file_path(self):
        old_path = self.modified_file.old_path
        new_path = self.modified_file.new_path
        if old_path:
            old_path = old_path.replace("\\", "/")
        if new_path:
            new_path = new_path.replace("\\", "/")
        return new_path, old_path

    def map_new_to_old_line(self, src_line):
        return self.map_line_by_offset(src_line, self.new_to_old_line_offset)

    def map_old_to_new_line(self, src_line):
        return self.map_line_by_offset(src_line, self.old_to_new_line_offset)

    def get_add_blocks(self):
        return self.add_blocks

    def get_delete_blocks(self):
        return self.delete_blocks

    def get_new_to_old_line_offset(self):
        return self.new_to_old_line_offset

    def get_old_to_new_line_offset(self):
        return self.old_to_new_line_offset

    def get_new_line_content(self, line_number):
        if self.new_lines_content is None:
            return None
        if line_number <= 0 or len(self.new_lines_content) < line_number:
            return None
        return self.new_lines_content[line_number - 1]

    def get_old_line_content(self, line_number):
        if self.old_lines_content is None:
            return None
        if line_number <= 0 or len(self.old_lines_content) < line_number:
            return None
        return self.old_lines_content[line_number - 1]

    @staticmethod
    def is_block_complete_covered_function(function_start, function_end, block):
        block_start = block[0]
        block_end = block[1]
        return block_start <= function_start and block_end >= function_end

    @staticmethod
    def is_block_part_covered_function(function_start, function_end, block):
        block_start = block[0]
        block_end = block[1]
        return ((block_start >= function_start and block_end <= function_end) or
                (block_start <= function_start <= block_end) or
                (block_start <= function_end <= block_end))

    @staticmethod
    def find_no_modify_lines(function_start, function_end, block_list):
        no_modify_lines = []
        func_lines = list(range(function_start, function_end + 1))
        covered_lines = set()
        for block in block_list:
            block_start = block[0]
            block_end = block[1]
            if block_start > function_end:
                break
            covered_lines.update(list(range(block_start, block_end + 1)))
        for line in func_lines:
            if line not in covered_lines:
                no_modify_lines.append(line)
        return no_modify_lines

    @staticmethod
    def map_line_by_offset(src_line, line_offset_map):
        # 新旧函数行数映射
        to_line = -1
        for idx, map_info in enumerate(line_offset_map):
            if to_line == -1 and map_info[0] > src_line:
                to_line = src_line + line_offset_map[idx - 1][1]
                break
        return to_line

    @staticmethod
    def get_line_block(lines):
        if not lines:
            return []
        blocks = []
        start = lines[0][0]
        prev = start
        for current in lines[1:]:
            if current[0] != prev + 1:  # 检测连续性中断
                blocks.append([start, prev])  # 保存当前连续块
                start = current[0]  # 开始新的连续块
            prev = current[0]
        # 添加最后一个块
        blocks.append([start, prev])
        return blocks

    @staticmethod
    def get_from_to_offset_map(add_blocks, delete_blocks):
        new_to_old = [[0, 0]]
        temp_map = []
        cur_offset = 0
        for block in add_blocks:
            block_size = block[1] - block[0] + 1
            cur_offset -= block_size
            temp_map.append([block[1] + 1, cur_offset])
        cur_offset = 0
        cur_tmp_index = 0
        for block in delete_blocks:
            block_size = block[1] - block[0] + 1
            pre_offset = cur_offset
            cur_offset += block_size
            start_line = block[1] + 1 - cur_offset
            if cur_tmp_index < len(temp_map):
                cur_old_line = temp_map[cur_tmp_index][0] + temp_map[cur_tmp_index][1]
                # add 映射的行在 delete 映射之前
                while cur_old_line <= start_line:
                    if cur_old_line == start_line:
                        new_to_old.append([temp_map[cur_tmp_index][0], temp_map[cur_tmp_index][1] + cur_offset])
                    else:
                        new_to_old.append([temp_map[cur_tmp_index][0], temp_map[cur_tmp_index][1] + pre_offset])
                    cur_tmp_index += 1
                    if cur_tmp_index == len(temp_map):
                        break
                    cur_old_line = temp_map[cur_tmp_index][0] + temp_map[cur_tmp_index][1]
            add_offset = 0
            if cur_tmp_index != 0:
                add_offset = temp_map[cur_tmp_index - 1][1]
            new_to_old.append([start_line - add_offset, add_offset + cur_offset])
        while cur_tmp_index < len(temp_map):
            new_to_old.append([temp_map[cur_tmp_index][0], temp_map[cur_tmp_index][1] + cur_offset])
            cur_tmp_index += 1
        new_to_old = sorted(new_to_old, key=lambda x: x[0])
        new_to_old.append([sys.maxsize, new_to_old[-1][1]])
        return new_to_old
